package org.liang.transfer.ftp;

import java.io.IOException;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.liang.transfer.Transfer;
import org.liang.transfer.exception.UnconnectedException;
import org.liang.transfer.exception.UncorrectedRemotePathException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * FTP 客户端.
 * 
 * <pre>
 * 如果未指定端口，则使用默认端口（21）。
 * 操作完成后续手动调用断开连接方法{@link FTP#disconnect()}进行连接释放。
 * </pre>
 * 
 * @author L.Yang
 * @version v1.0 2016年4月7日 下午4:16:49
 */
public class FTP implements Transfer {

    private static final Logger LOGGER = LoggerFactory.getLogger(FTP.class);

    private static final int DEF_PORT = 21;

    private FTPClient ftp;

    private boolean isconnected;
    
    @Override
    public void connect(String host, String username, String password) {
        connect(host, DEF_PORT, username, password);
    }

    @Override
    public void connect(String host, int port, String username, String password) {
        try {
            ftp = new FTPClient();
            ftp.setControlEncoding("GBK");

            LOGGER.trace("开始连接 FTP  {} : {} ", new Object[] {host, port});
            ftp.connect(host, port);
            int reply = ftp.getReplyCode();
            LOGGER.trace("FTP 响应值 : {}", reply);
            if (!FTPReply.isPositiveCompletion(reply)) {
                disconnect();
                LOGGER.error("FTP 服务器拒绝连接。");
            } else {
                boolean islogin = ftp.login(username, password);
                LOGGER.trace("FTP 登录状态 ： {}", islogin);
                ftp.setDataTimeout(18 * 1000);
                ftp.setConnectTimeout(5 * 60 * 1000);
                ftp.enterLocalPassiveMode();
                ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
                isconnected = true;
            }
        } catch (IOException e) {
            LOGGER.error("FTP 登录失败 :{}", ExceptionUtils.getStackTrace(e));
        }
    }

    /*
     * 上传文件到服务器.
     * 
     * @param remote 远程FTP服务器文件路径
     * 
     * @param local 本地存储文件的完整路径（包含文件名）
     */
    @Override
    public void upload(String remote, String local) {
        try {
            if (!isconnected) throw new UnconnectedException();
            FTPUploader uploader = new FTPUploader(ftp);
            uploader.upload(remote, local);
        } catch (UnconnectedException e) {
            LOGGER.error("SFTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
            throw e;
        } catch (Exception e) {
            disconnect();
            LOGGER.error("FTP 上传失败 :{}", ExceptionUtils.getStackTrace(e));
        }
    }

    /*
     * 下载远程服务器上的文件，并存储到本地.
     * 
     * <pre> 
     * 如果remote 是文件夹则将文件夹中的文件全部下载；
     * 如果local 是文件名则使用locl 如果local 是路径则使用服务器文件名；
     * </pre>
     * 
     * @param remote 远程FTP服务器文件的完整路径
     * 
     * @param local 本地存储文件的完整路径
     * 
     * @throws UncorrectedRemotePathException 当远程路径不存在是抛出此异常
     */
    @Override
    public void download(String remote, String local) {
        try {
            if (!isconnected) throw new UnconnectedException();
            FTPDownloader downloader = new FTPDownloader(ftp);
            downloader.download(remote, local, null);
        } catch (UncorrectedRemotePathException e) {
            LOGGER.error("FTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
            disconnect();
            throw e;
        } catch (UnconnectedException e) {
            LOGGER.error("SFTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
            throw e;
        } catch (Exception e) {
            disconnect();
            LOGGER.error("FTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
        }
    }

    /*
     * 下载远程服务器上的文件，并存储到本地.
     * 
     * <pre>
     * remote 如果是文件夹则将对文件夹中的文件进行filter过滤，将符合filter的文件下载； 
     * 如果local 是文件名则使用locl 如果local是路径则使用服务器文件名；
     * 
     * 过滤规则： 
     *  远程文件命中包含filter字符串。 
     *  
     *  tips： 文件夹必须包含结束字符串，否则认定为文件 。
     *  在文件目录中过滤符合名称的文件进行下载 
     * </pre>
     * 
     * @param remote 远程FTP服务器文件的完整路径
     * 
     * @param local 本地存储文件的完整路径
     * 
     * @param filter 过滤内容
     * 
     * @throws UncorrectedRemotePathException 当远程路径不存在是抛出此异常
     */
    @Override
    public void download(String remote, String local, String filter) {
        try {
            if (!isconnected) throw new UnconnectedException();
            FTPDownloader downloader = new FTPDownloader(ftp);
            downloader.download(remote, local, filter);
        } catch (UncorrectedRemotePathException e) {
            LOGGER.error("FTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
            disconnect();
            throw e;
        } catch (UnconnectedException e) {
            LOGGER.error("SFTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
            throw e;
        } catch (Exception e) {
            disconnect();
            LOGGER.error("FTP 下载失败 :{}", ExceptionUtils.getStackTrace(e));
        }
    }

    /*
     * 断开连接.
     * 
     * <pre> 下载完成后需手动调用此方法来关闭ftp连接。 </pre>
     */
    @Override
    public void disconnect() {
        if (ftp != null && ftp.isConnected()) {
            try {
                ftp.disconnect();
                isconnected = false;
            } catch (IOException e) {
                LOGGER.error(ExceptionUtils.getStackTrace(e));
            }
        }
        LOGGER.trace("FTP已断开连接。");
    }
}
